package com.aabte.commons.errorcode;

import com.aabte.commons.bean.ErrorResponse;
import com.aabte.commons.errorcode.enums.BaseResponseCode;
import com.aabte.commons.errorcode.exception.BaseException;
import com.aabte.commons.errorcode.exception.BusinessException;
import com.aabte.commons.util.ExceptionUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;

/**
 * @author Daniel
 * @version 1.0
 * @date 2020/4/30
 */
@Slf4j
@Component
@ControllerAdvice
@ConditionalOnWebApplication
public class UnifiedExceptionHandler {

  /**
   * 生产环境
   */
  private final static String ENV_PROD = "prod";

  @Resource
  private UnifiedMessageSource messageSource;

  /**
   * 当前环境
   */
  @Value("${spring.profiles.active:''}")
  private String profile;

  /**
   * 获取国际化消息
   *
   * @param e 异常
   * @return
   */
  public String getMessage(BaseException e) {
    String template = e.getErrorMessageTemplate();
    String message = messageSource.getMessage(template, e.getArgs());
    if (message == null || message.isEmpty()) {
      return e.getResponseCode().message();
    }
    return message;
  }

  /**
   * 业务异常
   *
   * @param e 异常
   * @return 异常结果
   */
  @ExceptionHandler(value = BusinessException.class)
  @ResponseBody
  public ErrorResponse handleBusinessException(HttpServletResponse response, BaseException e) {
      log.warn(ExceptionUtils.getErrorStack(e));
    IResponseCode responseCode = e.getResponseCode();
    response.setStatus(responseCode.status());
    return new ErrorResponse(responseCode.code(), getMessage(e));
  }

  /**
   * 自定义异常
   *
   * @param e 异常
   * @return 异常结果
   */
  @ExceptionHandler(value = BaseException.class)
  @ResponseBody
  public ErrorResponse handleBaseException(HttpServletResponse response, BaseException e) {
      log.warn(ExceptionUtils.getErrorStack(e));
    IResponseCode responseCode = e.getResponseCode();
    response.setStatus(responseCode.status());
    return new ErrorResponse(responseCode.code(), getMessage(e));
  }

  /**
   * Controller上一层相关异常
   *
   * @param e 异常
   * @return 异常结果
   */
  @ExceptionHandler({
      MethodArgumentTypeMismatchException.class,
      NoHandlerFoundException.class,
      HttpRequestMethodNotSupportedException.class,
      HttpMediaTypeNotSupportedException.class,
      MissingPathVariableException.class,
      MissingServletRequestParameterException.class,
      TypeMismatchException.class,
      HttpMessageNotReadableException.class,
      HttpMessageNotWritableException.class,
      HttpMediaTypeNotAcceptableException.class,
      ServletRequestBindingException.class,
      ConversionNotSupportedException.class,
      MissingServletRequestPartException.class,
      AsyncRequestTimeoutException.class,
      ServletException.class
  })
  @ResponseStatus(code = HttpStatus.BAD_REQUEST)
  @ResponseBody
  public ErrorResponse handleServletException(ServletException e) {
      log.warn(ExceptionUtils.getErrorStack(e));
      int code = BaseResponseCode.BAD_REQUEST.code();
      if (ENV_PROD.equals(profile)) {
          // 当为生产环境, 不适合把具体的异常信息展示给用户, 比如404.
          BaseException baseException = new BaseException(BaseResponseCode.BAD_REQUEST);
          String message = getMessage(baseException);
          return new ErrorResponse(code, message);
      }
      return new ErrorResponse(code, e.getMessage());
  }


  /**
   * 参数绑定异常
   *
   * @param e 异常
   * @return 异常结果
   */
  @ExceptionHandler(value = BindException.class)
  @ResponseStatus(code = HttpStatus.BAD_REQUEST)
  @ResponseBody
  public ErrorResponse handleBindException(BindException e) {
      log.warn(ExceptionUtils.getErrorStack(e));
    return wrapperBindingResult(e.getBindingResult());
  }

  /**
   * 参数校验异常，将校验失败的所有异常组合成一条错误信息
   *
   * @param e 异常
   * @return 异常结果
   */
  @ExceptionHandler(value = MethodArgumentNotValidException.class)
  @ResponseStatus(code = HttpStatus.BAD_REQUEST)
  @ResponseBody
  public ErrorResponse handleValidException(MethodArgumentNotValidException e) {
      log.warn(ExceptionUtils.getErrorStack(e));
    return wrapperBindingResult(e.getBindingResult());
  }

  /**
   * 包装绑定异常结果
   *
   * @param bindingResult 绑定结果
   * @return 异常结果
   */
  private ErrorResponse wrapperBindingResult(BindingResult bindingResult) {
    StringBuilder msg = new StringBuilder();

    for (ObjectError error : bindingResult.getAllErrors()) {
      msg.append(", ");
      if (error instanceof FieldError) {
        msg.append(((FieldError) error).getField()).append(": ");
      }
      msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());

    }

      return new ErrorResponse(BaseResponseCode.BAD_REQUEST.code(), msg.substring(2));
  }

  /**
   * 未定义异常
   *
   * @param e 异常
   * @return 异常结果
   */
  @ExceptionHandler(value = Exception.class)
  @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
  @ResponseBody
  public ErrorResponse handleException(Exception e) {
      log.warn(ExceptionUtils.getErrorStack(e));

    if (ENV_PROD.equals(profile)) {
        // 当为生产环境, 不适合把具体的异常信息展示给用户, 比如数据库异常信息.
        int code = BaseResponseCode.INTERNAL_SERVER_ERROR.code();
        BaseException baseException = new BaseException(BaseResponseCode.INTERNAL_SERVER_ERROR);
        String message = getMessage(baseException);
        return new ErrorResponse(code, message);
    }

      return new ErrorResponse(BaseResponseCode.INTERNAL_SERVER_ERROR.code(), e.getMessage());
  }
}
